package com.zhiyuancode.main;

import com.zhiyuancode.conf.Bean;
import com.zhiyuancode.conf.Property;
import com.zhiyuancode.conf.Scope;
import com.zhiyuancode.parse.ConfigManager;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 杨志远[OF2551]
 * email faryangsh@163.com
 * Date    2018-11-23
 */
public class ClassPathXmlApplicationContext implements BeanFactory {
    private Map<String, Bean> config;

    // 作为spring的容器
    private Map<String, Object> context = new HashMap<String, Object>();

    public Object getBean(String beanName) {
        Object bean = context.get(beanName);

        // 如果bean的scope配置不为singleton，那么context中就不会包含该Bean对象
        if (bean == null) {
            bean = createBean(config.get(beanName));
        }

        return bean;
    }


    public ClassPathXmlApplicationContext(String path) {
        config = ConfigManager.getConfig(path);

        if (config != null) {
            for (Map.Entry<String, Bean> en : config.entrySet()) {
                // 获取配置中bean配置信息
                String beanName = en.getKey();
                Bean bean = en.getValue();

                // createBean方法会往context中放入bean，所以创建bean时需要判断context是否存在要创建的bean
                Object existBean = context.get(beanName);

                if (existBean == null && bean.getScope() == Scope.SINGLETON) {
                    // bean 配置，创建bean对象
                    Object beanObj = createBean(bean);
                    // 将初始化号的bean放入容器
                    context.put(beanName, beanObj);
                }

            }

        }
    }

    /**
     * 创建当前bean所对应的对象，并填充到容器中
     * - 对于当前依赖的bean，创建该依赖bean的对象，并往装配到当前bean对象中、依赖bean对象填充到容器中
     *
     * @param bean 当前bean
     * @return 当前bean的对象
     */
    private Object createBean(Bean bean) {
        // 获得创建bean 的 class
        Class clazz = null;
        try {
            clazz = Class.forName(bean.getClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("bean class配置错误,classname=" + bean.getClassName());
        }

        // 将class对应的对象创建出来
        try {
            Object beanObj = clazz.newInstance(); // 调用空参数构造

            if (bean.getProperties() != null) {
                for (Property prop : bean.getProperties()) {

                    //1. 注入配置为value值的属性
                    if (prop.getValue() != null) {
                        // 获得注入的属性值
                        String value = prop.getValue();
                        // 调用bean的属性值get方法，注入属性值
                        invokeMethodToAssembleValue(beanObj, prop, value);
                    }

                    //2. 注入配置为ref值的属性
                    if (prop.getRef() != null && config.get(prop.getRef()).getScope() == Scope.SINGLETON) {
                        // 从当前容器找需要注入的bean
                        Object existBean = context.get(prop.getRef());

                        if (existBean == null) {
                            // 说明容器中还不存在我们要注入的bean
                            // 将bean创建
                            existBean = createBean(config.get(prop.getRef()));
                            // 将创建好的bean放入容器
                            context.put(prop.getRef(), existBean);
                        }

                        // 注入引用对象bean的属性值
                        invokeMethodToAssembleValue(beanObj, prop, existBean);
                    }
                }

            }

            // 返回构造好的bean
            return beanObj;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("缺少空参构造");
        }
    }

    /**
     * 执行set方法，设置属性值到对象属性中
     *
     * @param beanObj    注入属性值的bean
     * @param prop       属性
     * @param fieldValue 属性值
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private void invokeMethodToAssembleValue(Object beanObj, Property prop, Object fieldValue)
            throws IllegalAccessException, InvocationTargetException {
        // 获得注入的属性名称
        String fieldName = prop.getName();
        // 根据属性名称获得注入属性对应的set方法
        Method setMethod = BeanUtils.getWriteMethod(beanObj, fieldName);
        //调用set方法注入
        setMethod.invoke(beanObj, fieldValue);
    }
}
